package com.tangguangdi.shield.server.permission;

import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import com.tangguangdi.base.common.entity.register.ApiDefinition;
import com.tangguangdi.base.common.entity.register.AppDefinition;
import com.tangguangdi.base.common.entity.register.UserDefinition;
import com.tangguangdi.base.core.register.RegisterServer;
import org.springframework.cloud.gateway.filter.FilterDefinition;
import org.springframework.cloud.gateway.handler.predicate.PredicateDefinition;
import org.springframework.cloud.gateway.route.RouteDefinition;
import org.springframework.web.util.UriComponentsBuilder;

import java.net.URI;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;

import static com.tangguangdi.base.common.enums.register.AppType.matchCode;


public class PermissionCache {

    private Cache<String, AppDefinition> apps;
    private Cache<String, Cache<String, ApiDefinition>> apis;

    private RegisterServer registerServer;

    public PermissionCache(RegisterServer registerServer) {
        this.registerServer = registerServer;
        this.apps = Caffeine.newBuilder()
                .build();
        this.apis = Caffeine.newBuilder()
                .build();
        init();
    }

    private void init(){
        List<AppDefinition> appDefinitions = registerServer.apps();
        for(AppDefinition appDefinition : appDefinitions){
            this.apps.put(appDefinition.getName(), appDefinition);
        }
        Set<String> appNames = this.apps.asMap().keySet();
        Map<String, List<ApiDefinition>> apiDefinitions = registerServer.apis(appNames);
        for(String name : apiDefinitions.keySet()){
            this.apis.put(name, getApiCache(apiDefinitions.get(name)));
        }
    }

    public void update(String appName){
        AppDefinition appDefinition = registerServer.app(appName);
        if(appDefinition != null){
            this.apps.put(appDefinition.getName(), appDefinition);
            List<ApiDefinition> apiDefinitions = registerServer.apis(appName);
            this.apis.put(appName, getApiCache(apiDefinitions));
        }
    }

    private Cache<String, ApiDefinition> getApiCache(List<ApiDefinition> apiDefinitions){
        Cache<String, ApiDefinition> cache = Caffeine.newBuilder().build();
        for(ApiDefinition apiDefinition : apiDefinitions) {
            StringBuilder builder = new StringBuilder();
            builder.append(apiDefinition.getUrl());
            builder.append(":");
            builder.append(apiDefinition.getMethod());
            cache.put(builder.toString(), apiDefinition);
        }
        return cache;
    }

    public Cache<String, AppDefinition> getApps() {
        return this.apps;
    }

    public Cache<String, ApiDefinition> getApis(String appName) {
        Cache<String, ApiDefinition> apiCache = this.apis.getIfPresent(appName);
        return apiCache;
    }

    public UserDefinition getUser(String token) {
        return this.registerServer.user(token);
    }

    public List<RouteDefinition> getRouteDefinitions() {
        List<RouteDefinition> routeDefinitions = new ArrayList<>();
        for (String key : this.apps.asMap().keySet()) {
            AppDefinition app = this.apps.getIfPresent(key);
            URI uri = getUri(app);
            if(uri != null) {
                RouteDefinition route = new RouteDefinition();
                route.setId(app.getName());
                route.setUri(uri);
                route.setPredicates(getPredicateDefinitions(app));
                route.setFilters(getFilter(app));
                routeDefinitions.add(route);
            }
        }
        return routeDefinitions;
    }

    private URI getUri(AppDefinition app) {
        switch (matchCode(app.getType())){
            case MaxServer:
                return UriComponentsBuilder.fromUriString("lb://".concat(app.getName())).build().toUri();
            case OtherServer:
                return UriComponentsBuilder.fromUriString("http://".concat(app.getName())).build().toUri();
            case MaxGateway:
            default:
                return null;
        }
    }

    private List<PredicateDefinition> getPredicateDefinitions(AppDefinition app){
        List<PredicateDefinition> predicates = new ArrayList<>();
        PredicateDefinition predicate = new PredicateDefinition();
        predicate.setName("Path");
        predicate.addArg("_genkey_0", app.getPrefix().concat("/**"));
        predicates.add(predicate);
        return predicates;
    }

    private List<FilterDefinition> getFilter(AppDefinition app){
        List<FilterDefinition> filters = new ArrayList<>();
        FilterDefinition filter = new FilterDefinition();
        filter.setName("StripPrefix");
        filter.addArg("_genkey_0", app.getPrefixLevel().toString());
        filters.add(filter);
        return filters;
    }
}